// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "Tests/AutomationCommon.h"
#include "Tests/AutomationEditorCommon.h"
#include "UObject/Object.h"
#include "Engine/ObjectLibrary.h"

#include "SimplygonTestsHelper.h"
#include "LODRecipe.h"
#include "LODRecipeManager.h"
#include "SimplygonPropertyCasters.h"
#include "SimplygonModule.h"

DEFINE_LOG_CATEGORY_STATIC(LogSimplygonLODRecipeTests, All, All);

BEGIN_DEFINE_SPEC(FSimplygonLODRecipe, "Simplygon.LODRecipe", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
	// Add shared members and methods here
	// For example:
	//int32 UsefulInteger = 0;
	//void UtilityMethodForTest();

	void TestTexturePixelFormat(
		const EOutputPixelFormat SimplygonPixelFormat, 
		const ETextureSourceFormat ExpectedTextureFormat,
		const TextureGroup ExpectedTextureGroup);
END_DEFINE_SPEC(FSimplygonLODRecipe)

void FSimplygonLODRecipe::Define() 
{
	// Geometry Data Casting
	Describe("Geometry Data Casting", [this]() 
	{
		BeforeEach([this]()
		{
			

		});

		It("Verify that MaterialChannel for GeometryDataCaster is properly set", [this]()
		{
			// Create LOD Recipe dynamically
			ULODRecipe* LODRecipe = SimplygonTestsHelper::CreateLODRecipe();
			LODRecipe->SetNumLODs(2);

			ISimplygonModule& SimplygonModule = FModuleManager::GetModuleChecked<ISimplygonModule>("Simplygon");
			
			UReductionPipeline* LOD1Pipeline = Cast<UReductionPipeline>(LODRecipe->GetPipelines()[1]);
			FReductionPipelineSettings& Settings = LOD1Pipeline->Settings;

			USimplygonGeometryDataCaster* GeomDataCaster = NewObject<USimplygonGeometryDataCaster>();
			GeomDataCaster->GeometryDataCasterSettings.GeometryDataFieldIndex = 7;
			GeomDataCaster->GeometryDataCasterSettings.GeometryDataFieldType = EGeometryDataFieldType::TriangleIds;
			Settings.MaterialPropertyCasters.Add( GeomDataCaster );

			const FString ExpecterMaterialChannel = TEXT("TriangleIds7");
			const int32 ExpectedNumMaterialCasters = 1;

			FString ErrorMsg;
			bool ValidPipelineSettings = LOD1Pipeline->HasValidSettings(ErrorMsg);
			TestEqual("Valid pipeline settings set", ValidPipelineSettings, true);

			Simplygon::spPipeline Pipeline = LOD1Pipeline->CreatePipeline(SimplygonModule.GetSDK());
			TestEqual("Number of material casters", Pipeline->GetMaterialCasterCount(), ExpectedNumMaterialCasters );
			
			auto MaterialCasters = Pipeline->GetMaterialCasters();
			Simplygon::rhandle CasterHandle = Pipeline->GetMaterialCasters()->GetFirstItem();
			auto GeometryCaster = Simplygon::spGeometryDataCaster::SafeCast(MaterialCasters->GetItemsObject(CasterHandle));
			
			FString ActualMaterialChannelName = ANSI_TO_TCHAR(GeometryCaster->GetGeometryDataCasterSettings()->GetMaterialChannel());
			TestEqual("Material channel", ActualMaterialChannelName, ExpecterMaterialChannel);
		});

		It("Casting a 8-bit texture using a single Geometry Data Caster", [this]()
		{
			TestTexturePixelFormat(EOutputPixelFormat::R8G8B8A8, TSF_BGRA8, TEXTUREGROUP_8BitData);
		});

		It("Casting a 16-bit texture using a single Geometry Data Caster", [this]()
		{
			TestTexturePixelFormat(EOutputPixelFormat::R16G16B16A16, TSF_RGBA16F, TEXTUREGROUP_16BitData);
		});

		It("Casting Base Color, Normal and Geometry Data Caster", [this]()
		{
			const int32 TextureSize = 256;
			UStaticMesh* StaticMesh = SimplygonTestsHelper::GetAsset<UStaticMesh>(ESimplygonTestAsset::SM_Cube);
			if(!StaticMesh)
				return;

			// Create LOD Recipe dynamically
			ULODRecipe* LODRecipe = SimplygonTestsHelper::CreateLODRecipe();
			LODRecipe->AddMesh(StaticMesh);
			LODRecipe->SetNumLODs(2);

			// Base Color Caster
			UBaseColorCaster* BaseColorCaster = NewObject<UBaseColorCaster>();

			// Normal Caster
			UNormalCaster* NormalCaster = NewObject<UNormalCaster>();

			// Geometry Data Caster
			USimplygonGeometryDataCaster* GeomDataCaster = NewObject<USimplygonGeometryDataCaster>();
			GeomDataCaster->GeometryDataCasterSettings.GeometryDataFieldIndex = 137;
			GeomDataCaster->GeometryDataCasterSettings.GeometryDataFieldType = EGeometryDataFieldType::Coords;
			GeomDataCaster->GeometryDataCasterSettings.OutputPixelFormat = EOutputPixelFormat::R8G8B8A8;

			UReductionPipeline* PipelineLOD1 = Cast<UReductionPipeline>(LODRecipe->GetPipelines()[1]);
			FReductionPipelineSettings& Settings = PipelineLOD1->Settings;
			Settings.ReductionSettings.ReductionTargetTriangleRatio = 1.0f;
			Settings.MappingImageSettings.OutputMaterialSettings[0].TextureHeight = TextureSize;
			Settings.MappingImageSettings.OutputMaterialSettings[0].TextureWidth = TextureSize;
			Settings.MaterialPropertyCasters.Add( BaseColorCaster );
			Settings.MaterialPropertyCasters.Add( NormalCaster );
			Settings.MaterialPropertyCasters.Add( GeomDataCaster );

			// LODRecipe adds assets to the asset registry so lets keep 
			// an eye on newly added assets.
			bool bWasGeometryDataAdded = false, bWasBaseColorAdded = false, bWasNormalAdded = false;
			FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
			auto OnAssetAddedDelegateHandle = AssetRegistryModule.Get().OnAssetAdded().AddLambda([this, &bWasGeometryDataAdded, &bWasBaseColorAdded, &bWasNormalAdded](const FAssetData& InAssetData)
			{
				const FString AssetName = InAssetData.AssetName.ToString();
				if(AssetName.Contains("Coords137"))
					bWasGeometryDataAdded = true;
				else if(AssetName.Contains("Diffuse"))
					bWasBaseColorAdded = true;
				else if(AssetName.Contains("Normal"))
					bWasNormalAdded = true;
			});

			// Generate LODs
			FLODRecipeManager::Get().Build(TArray<ULODRecipe*>{ LODRecipe });

			AssetRegistryModule.Get().OnAssetAdded().Remove(OnAssetAddedDelegateHandle);

			TestEqual("Generated geometry data texture", bWasGeometryDataAdded, true);
			TestEqual("Generated base color texture", bWasBaseColorAdded, true);
			TestEqual("Generated normal texture", bWasNormalAdded, true);
		});

		AfterEach([this]()
		{

		});
	
	});
}

void FSimplygonLODRecipe::TestTexturePixelFormat(
	const EOutputPixelFormat SimplygonPixelFormat, 
	const ETextureSourceFormat ExpectedTextureFormat,
	const TextureGroup ExpectedTextureGroup)
{
	const int32 TextureSize = 256;
	UStaticMesh* StaticMesh = SimplygonTestsHelper::GetAsset<UStaticMesh>(ESimplygonTestAsset::SM_Cube);
	if(!StaticMesh)
		return;

	// Create LOD Recipe
	ULODRecipe* LODRecipe = SimplygonTestsHelper::CreateLODRecipe();
	LODRecipe->AddMesh(StaticMesh);
	LODRecipe->SetNumLODs(2);

	USimplygonGeometryDataCaster* GeomDataCaster = NewObject<USimplygonGeometryDataCaster>();
	GeomDataCaster->GeometryDataCasterSettings.GeometryDataFieldIndex = 137;
	GeomDataCaster->GeometryDataCasterSettings.GeometryDataFieldType = EGeometryDataFieldType::Coords;
	GeomDataCaster->GeometryDataCasterSettings.OutputPixelFormat = SimplygonPixelFormat;

	UReductionPipeline* LOD1Pipeline = Cast<UReductionPipeline>(LODRecipe->GetPipelines()[1]);
	FReductionPipelineSettings& Settings = LOD1Pipeline->Settings;
	Settings.ReductionSettings.ReductionTargetTriangleRatio = 1.0f;
	Settings.MaterialPropertyCasters.Add( GeomDataCaster );
	Settings.MappingImageSettings.OutputMaterialSettings[0].TextureHeight = TextureSize;
	Settings.MappingImageSettings.OutputMaterialSettings[0].TextureWidth = TextureSize;

	ETextureSourceFormat ActualTextureFormat = TSF_MAX; 
	TextureGroup ActualTextureGroup = TEXTUREGROUP_MAX;
	// LODRecipe adds assets to the asset registry so lets keep 
	// an eye on newly added assets.
	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
	auto OnAssetAddedDelegateHandle = AssetRegistryModule.Get().OnAssetAdded().AddLambda([this, &ActualTextureFormat, &ActualTextureGroup](const FAssetData& InAssetData)
	{
		const FString AssetName = InAssetData.AssetName.ToString();
		if(AssetName.Contains("Coords137"))
		{
			UTexture2D* GeometryDataTexture = Cast<UTexture2D>(InAssetData.GetAsset());
			bool bIsValidTexture = TestNotNull("Generated Geometry Data Texture (UTexture2D)", GeometryDataTexture);
			if(!bIsValidTexture)
				return;

			ActualTextureFormat = GeometryDataTexture->Source.GetFormat();
			ActualTextureGroup = GeometryDataTexture->LODGroup;
		}
	});

	// Generate LODs
	FLODRecipeManager::Get().Build(TArray<ULODRecipe*>{ LODRecipe });

	AssetRegistryModule.Get().OnAssetAdded().Remove(OnAssetAddedDelegateHandle);

	TestEqual("Texture source format", ActualTextureFormat, ExpectedTextureFormat);
	TestEqual("Texture group (LODGroup)", ActualTextureGroup, ExpectedTextureGroup);
}